﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Tuxedo.Compile
{
	internal class SwitchStatement : CodeLine
	{
		private Expression[] values; // if the last member is null, then that indicates default
		private Expression condition;
		private CodeLine[][] code; // null in this array indicates the case is a fallthrough

		public SwitchStatement(Expression condition, IList<Expression> values, IList<CodeLine[]> caseCode)
		{
			this.condition = condition;
			this.values = values.ToArray();
			this.code = caseCode.ToArray();
		}

		public static CodeLine Parse(TokenStream tokens)
		{
			tokens.PopExpected("switch");
			tokens.PopExpected("(");
			Expression condition = Expression.ParseExpression(tokens);
			tokens.PopExpected(")");
			tokens.PopExpected("{");

			List<Expression> caseValues = new List<Expression>();
			List<CodeLine[]> caseCode = new List<CodeLine[]>();
			List<CodeLine> lines = new List<CodeLine>();

			while (!tokens.PopIfPresent("}"))
			{
				string next = tokens.PeekValue();
				if (next == "case")
				{
					tokens.PopExpected("case");
					caseValues.Add(Expression.ParseExpression(tokens));
					tokens.PopExpected(":");
				}
				else
				{
					tokens.PopExpected("default");
					tokens.PopExpected(":");
				}

				while (!tokens.IsNext("case") && !tokens.IsNext("default") && !tokens.IsNext("}"))
				{
					lines.Add(CodeLine.ParseLine(tokens));
				}

				caseCode.Add(lines.ToArray());
				lines.Clear();
			}

			SwitchStatement.VerifySamePrimitiveType(caseValues);

			return new SwitchStatement(condition, caseValues, caseCode);
		}

		private static void VerifySamePrimitiveType(List<Expression> caseValues)
		{
			if (caseValues.Count == 0) throw new Exception("switch statement must have cases");

			int effectiveLength = caseValues.Count;
			if (caseValues[caseValues.Count - 1] == null)
			{
				effectiveLength--; // don't check the last since it's a default
			}

			if (effectiveLength == 0) return; // apparently it's okay to only have a default

			int type = ConvertTypeToId(caseValues[0]);
			if (type == 0) throw new Exception("This is not a valid value for a switch case.");
			for (int i = 1; i < effectiveLength; ++i)
			{
				int otherType = ConvertTypeToId(caseValues[i]);
				if (otherType == 0) throw new Exception("This is not a valid value for a switch case.");
				if (type != otherType)
				{
					throw new Exception("Switch case values must all be the same type.");
				}
			}
		}

		private static int ConvertTypeToId(Expression expression)
		{
			expression = expression.EffectiveValue;

			if (expression is IntegerConstant) return INT_TYPE;
			if (expression is LongConstant) return INT_TYPE;
			if (expression is BooleanConstant) return BOOL_TYPE;
			if (expression is CharConstant) return CHAR_TYPE;
			if (expression is StringConstant) return STRING_TYPE;
			return INVALID_TYPE;
		}

		private const int INVALID_TYPE = 0;
		private const int BOOL_TYPE = 1;
		private const int INT_TYPE = 2;
		private const int CHAR_TYPE = 3;
		private const int STRING_TYPE = 4;

	}
}
